/**
 * ЯegExpR profiler - Using jQuery
 * Copyright 2010
 * Released under the MIT and GPL licenses.
 * 
 * Author      : y@s
 * Version     : 0.0.2
 * Published   : 2010-05-07
 * Last Update : 2010-05-14
 */
;(function( $, undefined ){
var dsc = {
	"\\b"  : "単語境界(word boundary)",
	"\\B"  : "単語境界以外(not word boundary)",
	"\\d"  : "半角数字(digit character)",
	"\\D"  : "半角数字以外(not digit character)",
	"\\f"  : "改ページ(form-feed)",
	"\\n"  : "改行(linefeed)",
	"\\r"  : "行頭復帰(carriage return)",
	"\\s"  : "空白文字類(space)",
	"\\S"  : "空白文字類以外(not space)",
	"\\t"  : "タブ(tab)",
	"\\v"  : "垂直タブ(vertical tab)",
	"\\w"  : "半角英数字とアンダースコア([A-Za-z0-9_])",
	"\\W"  : "半角英数字とアンダースコア以外([^A-Za-z0-9_])",
	"\\0"  : "NUL文字",
	"."    : "改行文字以外",
	"^"    : "先頭",
	"$"    : "末尾",
	"*"    : "0回以上の繰り返し",
	"*?"   : "0回以上の繰り返しの最小数",
	"+"    : "1回以上の繰り返し",
	"+?"   : "1回以上の繰り返しの最小数",
	"?"    : "0回か1回",
	"??"   : "0回か1回の最小数",
	"[\\b]": "バックスペース(backspace)",
	"(?!)" : "否定先読み",
	"(?:)" : "キャプチャしない括弧",
	"(?=)" : "(肯定)先読み"
};

function uniq( ary ){
	for( var i = 0 ; i < ary.length - 1 ; ){
		if ( ary.indexOf( ary[i], i + 1 ) !== -1 ) {
			ary.splice( i, 1 );
		}else{
			i++;
		}
	}
	return ary;
}

//右側にある文字列(パターン)を返します。
function getLeftPattern( s ){
	
	var lastChar;
	
	//文字列の長さが1、または1以下の場合は処理を終了します。
	if( s.length <= 1 ){
		return s;
	}
	
	//get last character
	//一番右にある文字を取得します。
	var lastChar = s.slice(-1);
	
	//Is escaped?
	if ( isEscaped(s) ) {
		//右から二文字を返します。 ???
		return s.slice(-2);
	}
	
	//\uhhhh
	if ( ( new RegExp(/\\u[0-9a-fA-F]{4}$/) ).test(s) &&
		!isEscaped( s.slice(0, s.length - 5) ) ) {
		return s.slice(-6);
	}
	
	//\xhh
	if ( ( new RegExp(/\\x[0-9a-fA-F]{2}$/) ).test(s) &&
		!isEscaped( s.slice(0, s.length - 3) ) ) {
		return s.slice(-4);
	}
	
	//get last character
	//一番右にある文字を取得します。
	lastChar = s.slice(-1);
	
	switch(lastChar){
		case "]":
			//exist "[",  deny "\["  
			return ( new RegExp(/^\[|[^\\]\[/) ).test(s)
				? getLastBrackets(s)
				: "]";
		case ")":
			//exist "(",  deny "\("  
			return ( new RegExp(/^\(|[^\\]\(/) ).test(s)
				? getLastParen(s)
				: ")";
		case "}":
			//exist "{",  deny "\{"
			return ( new RegExp(/^\{|[^\\]\{/) ).test(s)
				? getLastQuantifiers(s)
				: "}";
		default:
			return s.slice(-1);
	}
}



//get last {}
function getLastQuantifiers( s ){
	var
	leftIndex  = lastIndexOf( s, "{" ),
	rightIndex = lastIndexOf( s, "}" ),
	q;
	
	if( leftIndex === -1 || rightIndex === -1 ){
		return "";
	}
	
	//{...}
	q = s.slice(
		lastIndexOf( s, "{" ),
		lastIndexOf( s, "}" ) + 1 );
	
	//{n} or {n,} or {n,n}
	//01などの文字列(err)かは判定していない。
	if( !( new RegExp(/^\{\d+(,(\d+)?)?\}$/) ).test(q) ){
		return s.slice(-1);
	}
	
	return getLeftPattern( s.slice( 0, s.lastIndexOf(q) ) ) + q;
}

//get last ()
function getLastParen( s ){

	var
	n     = 0,
	left  = indexOf(     s, "(" ),
	right = lastIndexOf( s, ")" ),
	c;
	
	if( left === -1 || right === -1 ){
		return "";
	}
	
	s = s.slice(left, right + 1);
	c = s;
	
	do{
		left  = lastIndexOf( c, "(" );
		right = lastIndexOf( c, ")" );
		
		//(...)
		if ( left < right ) {
			n++;
			c = c.slice(0, right);
		}
		//)...(
		//or nothing ")"
		else {
			n--;
			c = c.slice(0, left);
		}
		
	}while( n > 0 );
	
	return s.slice( c.length );
}

//get last []
function getLastBrackets( s ){
	var right = indexOf( s, "]" );
	
	while( right !== s.length - 1 && right !== -1 ){
		s     = s.slice(right + 1);
		right = indexOf(s, "]");
	}
	
	//exist "["  deny "\[" ?
	return s.slice( ( ( new RegExp(/^\[|[^\\]\[/ ) ).test(s)
		&& !isEscaped( s.slice( 0, s.indexOf("[") + 1 ) ) )
			? indexOf( s, "[" )
			: -1 );
}

//non-escaped char index
function indexOf( s, q, from ){
	var i = 0;
	from = typeof from == "number" && !isNaN( from ) ? from : 0;
	
	while ( ( i = s.indexOf( q , from ) ) !== -1 ){
		if( !isEscaped( s.slice(0, i + 1) )  ){
			break;
		}
		from = i + 1;
	}
	
	return i;
}
//non-escaped char last index
function lastIndexOf( s, q ){
	var i  = 0,
	    ln = s.length;
	
	while ( ( i = s.slice(0, ln).lastIndexOf(q) ) !== -1 ){
		if( !( isEscaped( s.slice(0, i + 1) ) ) ){
			break;
		}
		ln -= i;
	}
	
	return i;
}


//is last char escaped?
function isEscaped( s ){
	if( !s || s.length == 1 ){ // null or undefined or empty string
		return false;
	}
	
	//check match \.
	if( ( new RegExp(/^\\[\s\S]$/) ).test( s.slice(-2) ) ){
		if( s.length === 2 || s.charAt(s.length - 3) !== "\\" ){
			return true;
		}
		return arguments.callee( s.slice(0, -2) );
	}
	
	return false;
}

//is Exist charactor
function isExist(s, q){
	var i = 0;
	
	while( ( i = s.indexOf( q, i ) ) !== -1 ){
		if( !isEscaped( s.slice(0, i + 1) ) ){
			return true;
		}
		i += q.length;
	}
	
	return false;
}

$.regexpr["profiler"] = function( regexp ){
	
	var
	ret = [],
	s   = regexp.source,
	i   = 0,
	j   = s.length,
	c   = null,
	esc = false, // current state is escaped
	
	inB = false, // in Brakects []
	bli = -1,    // left brakect index
	bs  = [],    // paren's
	
	pii = 0,     // ps pushed base index
	pci = -1,    // current Paren's index
	pli = [],    // left paren's index
	ps  = [],    // () paren's
	pa  = [],    // (?!)
	pn  = [],    // (?:) non-capturing 
	pq  = [],    // (?=) 
	
	inQ = false, // in Quantifier {}
	qli = -1,    // left quantifier's index
	
	sci = 0,     // current selector's index
	sli = [],    // left selector's index
	ss  = [],    // split values
	
	us  = [],    // \uhhhh
	xs  = [],    // \xhh
	
	add = function( k , v ){
		ret.push( {
			key  : k,
			value: v || dsc[k] || "-"
			} );
	},
	tmp, tmp2, tmp3; //temp variable
	
	
	sli[sci] = -1;
	ss[sci]  = [];
	
	for( ; i < j ; i++ ){
		c = s.charAt(i);
		
		if ( esc ) {
			switch ( c ) {
				case "b":
					if( inB && s.charAt(i + 1) === "]" ){
						break;
					}
				case "B":
				case "d":
				case "D":
				case "f":
				case "n":
				case "r":
				case "s":
				case "S":
				case "t":
				case "v":
				case "w":
				case "W":
					add( "\\" + c );
					break;
				
				case "u":// \uhhhh
					tmp = s.slice(i + 1, i + 5);
					if( ( new RegExp(/^[0-9a-fA-F]{4}$/) ).test(tmp) ){
						us.push("\\u" + tmp);
						i += 4;
					}
					break;
				
				case "x":// \xhh
					tmp = s.slice(i + 1, i + 3);
					if( ( new RegExp(/^[0-9a-fA-F]{2}$/) ).test(tmp) ){
						xs.push("\\x" + tmp);
						i += 2;
					}
					break;
				
				case "0":
					if( !( new RegExp(/\d/) ).test( s.charAt(i + 1) ) ){
						add( "\\0" );
					}
					break;
			}
		}else {//not escaped
			if( inB ){ //in Brakects []
				if ( c === "]" ) {
					if ( bli !== -1 ) {
						tmp = s.slice(bli, i + 1);
						if (tmp === "[\\b]") {
							add(tmp);
						}
						else {
							bs.push(tmp);
						}
						bli = -1;//reset left brakect index
					}
					inB = false;//in Brakects false
				}
			}else{// not in Brackects []
				
				switch ( c ) {
					//^, $, .
					case "^":
					case "$":
					case ".":
						add(c);
						break;
					
					case "?":
					case "+":
					case "*":
						if( c !== "?" || !( s.charAt(i - 1) === "(" && !isEscaped( s.slice(0, i) ) ) ){
							tmp  = s.charAt(i + 1) === "?";
							tmp2 = c + (tmp ? "?" : "");
							tmp3 = getLeftPattern( s.slice(0, i) );
							add( tmp3 + tmp2, tmp3 + " が" + dsc[tmp2] );
							i = i + (tmp ? 1 : 0);
						}
						break;
					//{}
					case "{":
						if( isExist( s.slice(i + 1), "}") ){
							inQ = true;
							qli = i;
						}
						break;
					case "}":
						if( inQ ){
							tmp = s.slice(qli, i + 1);
							if( ( new RegExp(/^\{\d+(,(\d+)?)?\}$/) ).test( tmp ) ){
								 tmp2 = s.charAt(i + 1) === "?";
								 
								(function( q, isq ){
									var
									lp   = getLeftPattern( s.slice( 0, s.lastIndexOf(q) ) ),
									desc = "",
									tmp;
									
									if ( ( new RegExp(/^{\d+}$/) ).test(q) ) {
										desc = q.match( new RegExp(/^{(\d+)}$/) )[1] + "回";
									}
									else
									if ( ( new RegExp(/^{\d+,}$/) ).test(q) ) { 
										desc = q.match( new RegExp(/^{(\d+),}$/) )[1]
											+ "回以上"
											+ (isq ? "の最小数" : "");
										
										q += isq ? "?" :"";
									}
									else 
									if ( ( new RegExp(/^{\d+,\d+}$/) ).test(q) ) {
										tmp = q.match( new RegExp(/^{(\d+),(\d+)}$/) );
										desc = tmp[1] + "回以上" + tmp[2]
											+ "回以下"
											+ (isq ? "の最小数" : "");
										
										q += isq ? "?" :""; 
									}
									
									add( lp + q, lp + " が" + desc );
								})( tmp, tmp2 );
								
								i   = i + (tmp2 ? 1 : 0);
								qli = -1;
								inQ = false;
							}
						}
						break;
					
					//[]
					case "[":
						tmp = s.slice(i + 1);
						if ( isExist( s.slice(i + 1), "]") && !inB ) {
							bli = i;
							inB = true;
						}
						break;
					
					//()
					case "(":
						pli[++pci] = i;
						sli[++sci] = i;
						ss[sci] = [];
						if( !( new RegExp(/^\(\?[!:=]$/) ).test( s.slice(i, i + 3) ) ){
							ps[ps.length] = undefined;
						}
						break;
					case ")":
						tmp = s.slice( pli[pci], i + 1 ); //get ( ... )
						
						if ( tmp.charAt(1) === "?" ) {
							switch ( tmp.charAt(2) ) {
								case "!": //(?! ... )
									pa.push(tmp);
									break;
								case ":": //(?: ... )
									pn.push(tmp);
									break;
								case "=": //(?= ... )
									pq.push(tmp);
									break;
							}
						}else{
							pii = ps.length - 1;
							while( ps[pii] !== undefined ){
								pii--;
							}
							ps[pii] = tmp;
						}
						
						//|
						if( ss[sci].length ){
							ss[sci].push( s.slice(sli[sci] + 1, i) );
						}
						
						pci--;
						sci--;
						break;
					
					//|
					case "|":
						tmp = s.slice( sli[sci] + 1, i );
						if(ss[sci].length === 0){
							if ( ( new RegExp(/^\?[:!=]/) ).test(tmp) ) {
								tmp = tmp.slice(2);
							}
						}
						ss[sci].push( tmp );
						sli[sci] = i;
						break;
				}
			}
		}
		esc = c === "\\" ? !esc : false;
	}
	
	
	
	//\uhhhh
	if( us.length ){
		for( i = 0, j = us.length ; i < j ; i++ ){
			us[i] = us[i] + " : &quot;" + String.fromCharCode( parseInt(us[i].slice(-4), 16) ) + "&quot;";
		}
		add("\\uhhhh", uniq(us).join("<br/>"));
	}
	//\xhh
	if( xs.length ){
		for( i = 0, j = xs.length ; i < j ; i++ ){
			xs[i] = xs[i] + " : &quot;" + String.fromCharCode( parseInt( xs[i].slice(-2), 16 ) ) + "&quot;";
		}
		add("\\xhh", uniq(xs).join("<br/>"));
	}
	
	
	//()
	if( ps.length ){
		for( i = 0, j = ps.length ; i < j ; i++ ){
			ps[i] = "\\" + (i + 1) + ":" + ps[i];
		}
		add("()", ps.join("<br/>"));
	}
	
	//(?!), (?=), (?:)
	if( pa.length ){
		pa.unshift(dsc["(?!)"]);
		add("(?!)", pa.join("<br/>"));
	}
	if( pq.length ){
		pq.unshift(dsc["(?=)"]);
		add("(?=)", pq.join("<br/>"));
	}
	if( pn.length ){
		pn.unshift(dsc["(?:)"]);
		add("(?:)", pn.join("<br/>"));
	}
	
	//[]
	if( bs.length ){
		add("[]", bs.join("<br/>"));
	}
	

	//|
	if ( ss[sci].length ) {
		ss[sci].push( s.slice(sli[sci] + 1) );
	}
	
	for( tmp = [], i = 0, j = ss.length ; i < j ; i++ ){
		if( ss[i].length ){
			tmp.push( ss[i].join(" または ") );
		}
	}
	
	if ( tmp.length ) {
		add( "|", tmp.join("<br/>") );
	}
	//| end
	
	
	//uniq
	//重複した値(戻り値の内容)を削除します。
	for ( i = 0 ; i < ret.length - 1 ; i++ ) {
		tmp = ret[i].key;
		for ( j = i + 1 ; j < ret.length ; ) {
			if( ret[j].key === tmp ){
				ret.splice(j, 1);
			}else{
				j++;
			}
		}
	}
	
	return ret;
};
})(jQuery);